sleep

为了写一个视屏游戏, 需要把影响特定的时间置于特定的位置. 用 curses 把影像置 于特定的位置

动画例子: hello3.c

#include <stdio.h>
#include <curses.h>

int main()
{
    int i;
    initscr();
    clear();
    for (i = 0; i < LINES; i ++){
        move(i, i + i);
        if (i % 2 == 1)
            standout();
        addstr("Hello, world");
        if (i % 2 == 1)
            standend();
        sleep(1);
        refresh();
    }
    endwin();
    return 0;
}

动画例子: hello4.c

#include <stdio.h>
#include <curses.h>

int main()
{
    int i;
    initscr();
    clear();
    for (i = 0; i < LINES; i ++){
        move(i, i + i);
        if (i % 2 == 1)
            standout();
        addstr("Hello, world");
        if (i % 2 == 1)
            standend();
        refresh();
        sleep(1);
        move(i, i+i);
        addstr("                          ");
    }
    endwin();
    return 0;
}
动画例子: hello5.c
#include <curses.h>
#define LEFTEDGE 10
#define RIGHTDGE 30
#define ROW 10

int main()
{
    char* message = "Hello";
    char* blank   = "     ";
    int dir = +1;
    int pos = LEFTEDGE;
    initscr();
    clear();

    while(1){
        move(ROW, pos);
        addstr(message);
        move(LINES-1, COLS-1);
        refresh();
        sleep(1);
        move(ROW, pos);
        addstr(blank);
        pos += dir;
        if (pos >= RIGHTDGE)
            dir = -1;
        if (pos < LEFTEDGE)
            dir = +1;
    }
    return 0;
}

Alarms

前面3个例子里用 sleep 加入时延. 时钟的另一个用途是调度一个将来要做的任务, 就像拨好一个煮鸡蛋的定时器, 然后干别的事情知道定时器鸣叫.同样目的, Unix提供 alarm

添加时延: sleep

sleep(n) 将当前进程挂起 n 秒或者在此期间被一个不能忽略的信号的到达所唤醒.

sleep() 是如何工作的: 使用 Unix 中的 Alarms

sleep 函数的工作机理与你想睡定长时间的觉一样:

  1. 设置闹钟到你想睡的秒数
  2. 睡觉, 知道闹铃的铃声响起

系统中每个进程都有一个私有的闹钟(alarm clock). 设置一定秒数后的闹铃, 时间 一到, 时钟就发送一个信号 SIGALRM 到进程. 除非进程为 SIGALRM 设置了处理函数 ,否则信号将杀死这个进程.

sleep 函数由3个步骤组成:

  1. 为 SIGALRM 设置一个处理函数
  2. 调用 alarm(num_seconds);
  3. 调用 pause

系统调用 pause 挂起进程知道信号到达. 任何信号都可以唤醒进程, 而非仅等待 SIGALRM

sleep1.c

#include <stdio.h>
#include <signal.h>

// #define SHHHH


void wakeup(int);

int main()
{
    printf("about to sleep for 4 seconds\n");
    signal(SIGALRM, wakeup);
    alarm(4);
    pause();
    printf("Morning so soon? \n");
}

void wakeup(int signum)
{
#ifndef SHHHH
    printf("Alarm received from kernel\n");
#endif
}
调用 pause 的目的是挂起进程直到有一个信号被处理. 当计时器计时 4 秒钟以后, 内核送出 SIGALRM 给进程, 导致控制从 pause 跳转到信号处理函数.在信号处理程序 中的代码被执行, 然后控制返回. 当信号被处理完后, pause 返回, 进程继续.

  alarm
目标 设置发送信号的计时器
头文件 #include <unistd.h>
原型 unsigned old = alarm(unsigned seconds)
参数 seconds 等待时间(秒)
返回值 -1 error, old 计时器剩余时间
  pause
目标 等待信号
头文件 #include <unistd.h>
原型 result = pause()
参数  
返回值 总是-1

间隔计时器

精度为秒的时钟对于很多应用不能让人满意. 后来加进了一个被称为间隔计时器(interval timer)的概念. 有更高的精度. 而且每个进程都是3个独立的计时器. 每个计时器都有两个设置: 初始时间和重复间隔.

可以用这个新的系统来添加时延和为事件定时.

添加精度更高的时延: usleep

usleep(n) 将当前进程挂起 n 微妙或者知道有一个不能忽略的信号到达.

三种计时器: 真实、进程和实用

进程可以以3种方式来计时. 考虑一个程序在运行了 30s 后结束. 在一个分时系统中, 这个程序不是一直在运行的, 其他的程序与它共享处理器.

内核提供计时器来计量这三种类型的时间

(1) ITIMER_REAL

这个计时器计量真实时间, 不管程序在用户态还是和心态用了多少处理器时间它都记 录. 当这个计时器用尽, 发送 SIGALRM 消息.

(2) ITIMER_VIRTUAL

这个计时器只记录进程在用户态运行的时间. 虚拟计时器(virtual timer)的 30 s 比 实际计时器的(real timer)的 30s 要行. 当虚拟计时器用尽, 发送 SIGVTALRM 信号

(3) ITIMER_PROF

这个计时器在进程运行与用户态或由该进程调用而陷入和心态时的计时. 当这个计时 器用尽, 发送 SIGPROF 信号

两种间隔: 初始和重复

每个间隔计时器的设置都有这样两个参数: 初始时间和重复时间. 在间隔计时器用的 结构体中初始时间是 it_value, 重复间隔是 it_interval. 如果不想用重复这已特 征, 将 it_interval 设置为 0. 要把两个时钟都关掉, 设 it_value 为0.

用间隔计时器编程

程序中使用间隔计时器要复杂一点, 要选择计时器的类型, 然后需要选择初始间隔和 重复间隔, 还要设置在 struct itimerval 中的值, 然后将这个结构体通过调用 setitimer 传给计时器. 为了读取计时器设置, 使用 getitimer.

ticker_demo.c

程序 ticker_demo.c 演示了如何使用一个间隔计时器

/* ticker_demo.c
 */
#include <stdio.h>
#include <sys/time.h>
#include <signal.h>

void countdown(int);

int main()
{
    signal(SIGALRM, countdown);
    if (set_ticker(500) == -1)
        perror("set_ticker");
    else
        while(1)
            pause();
    return 0;
}

void countdown(int signum)
{
    static int num = 10;
    printf("%d..", num--);
    fflush(stdout);
    if (num < 0){
        printf("DONE!\n");
        exit(0);
    }
}

set_ticker.c

#include <stdio.h>
#include <sys/time.h>

int set_ticker(int n_mses)
{
    struct itimerval new_timeset;
    long n_sec, n_usecs;

    n_sec = n_mses / 1000;
    n_usecs = (n_mses % 1000) * 1000L;
    new_timeset.it_interval.tv_sec = n_sec;
    new_timeset.it_interval.tv_usec = n_usecs;
    new_timeset.it_value.tv_sec = n_sec;
    new_timeset.it_value.tv_usec = n_usecs;
    return setitimer(ITIMER_REAL, &new_timeset, NULL);
}

编译

cc set_ticker.c ticker_demo.c -o ticker_demo

系统调用小结

  getitimer
头文件 #include <sys/time.h>
原型 result = getitimer(int which, struct itimerval * val);
参数 which 计时器, val 获取值的指针
返回值 0 success, -1 error
  setitimer
头文件 #include <sys/time.h>
原型 result = setitimer(int which, const struct itimerval * newval, struct itmerval *oldval);
参数 which 计时器, newval 要被设置值的指针, oldval 被替换设置值的指针
返回 0 success, -1 error